package com.accesshollywood.p2p;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.List;

import peerbase.HandlerInterface;
import peerbase.LoggerUtil;
import peerbase.Node;
import peerbase.PeerConnection;
import peerbase.PeerInfo;
import peerbase.PeerMessage;
import peerbase.RouterInterface;
import peerbase.sample.FileShareNode;

import com.accesshollywood.SQL.GFXSQL;
import com.accesshollywood.grfxOrdering.GRFXOrdersMain;
import com.accesshollywood.indexables.User;
import com.accesshollywood.trace.Trace;

public class P2Pnode extends Node {
	/* MESSAGE TYPES */
	public static final String			INSERTPEER	= "JOIN";
	public static final String			LISTPEER	= "LIST";
	public static final String			PEERNAME	= "NAME";
	public static final String			QUERY		= "QUER";
	public static final String			QRESPONSE	= "RESP";
	public static final String			PEERQUIT	= "QUIT";
	public static final String			REFRESH		= "REFR";

	public static final String			REPLY		= "REPL";
	public static final String			ERROR		= "ERRO";

	/* CLASS MEMBERS */
	// mapping from filenames to peer-ids
	private Hashtable<String, String>	files;

	public P2Pnode(int maxPeers, PeerInfo myInfo) {
		super(maxPeers, myInfo);
		files = new Hashtable<String, String>();

		this.addRouter(new Router(this));

		this.addHandler(INSERTPEER, new JoinHandler(this));
		this.addHandler(LISTPEER, new ListHandler(this));
		this.addHandler(PEERNAME, new NameHandler(this));
		this.addHandler(QUERY, new QueryHandler(this));
		this.addHandler(QRESPONSE, new QResponseHandler(this));
		this.addHandler(PEERQUIT, new QuitHandler(this));
		this.addHandler(REFRESH, new RefreshHandler(this));
	}

	public void buildPeers(int hops) {
		if (this.maxPeersReached() || hops <= 0) {
			return;
		}

		GFXSQL.refreshConnections();
		for (String currIP : GFXSQL.connectedIPs) {
			PeerInfo pd = new PeerInfo(currIP, 8080);

			List<PeerMessage> resplist = this.connectAndSend(pd, PEERNAME, "", false);
			if (resplist == null || resplist.size() == 0) {
				// return;
			} else {
				String peerid = resplist.get(0).getMsgData();
				LoggerUtil.getLogger().fine("contacted " + peerid);
				pd.setId(peerid);

				String resp = this.connectAndSend(pd, INSERTPEER, String.format("%s %s %d", this.getId(), this.getHost(), this.getPort()), true).get(0).getMsgType();
				if (!resp.equals(REPLY) || this.getPeerKeys().contains(peerid)) {
					return;
				}

				this.addPeer(pd);
			}
		}
	}

	public void refreshPeers() {
		GFXSQL.refreshConnections();
		for (String currIP : GFXSQL.connectedIPs) {
			PeerInfo pd = new PeerInfo(currIP, 8080);

			List<PeerMessage> resplist = this.connectAndSend(pd, REFRESH, "NULL~~~ALL", false);
			if (resplist == null || resplist.size() == 0) {
				// return;
			} else {
				String peerid = resplist.get(0).getMsgData();
				LoggerUtil.getLogger().fine("contacted " + peerid);
			}
		}
	}

	public void refreshPeers(int[] orderNumbers) {
		GFXSQL.refreshConnections();
		for (String currIP : GFXSQL.connectedIPs) {
			PeerInfo pd = new PeerInfo(currIP, 8080);

			String orderNumbersString = "";
			if (orderNumbers != null) {
				for (int i : orderNumbers) {
					orderNumbersString += i + ",";
				}
				orderNumbersString = orderNumbersString.substring(0, orderNumbersString.lastIndexOf(","));
			} else {
				orderNumbersString = "ALL";
			}
			List<PeerMessage> resplist = this.connectAndSend(pd, REFRESH, "NULL~~~" + orderNumbersString, false);
			if (resplist == null || resplist.size() == 0) {
				// return;
			} else {
				String peerid = resplist.get(0).getMsgData();
				LoggerUtil.getLogger().fine("contacted " + peerid);
			}
		}
	}

	public void refreshPeers(String fieldName, int[] orderNumbers) {
		GFXSQL.refreshConnections();
		for (String currIP : GFXSQL.connectedIPs) {
			PeerInfo pd = new PeerInfo(currIP, 8080);

			String orderNumbersString = "";
			if (orderNumbers != null) {
				for (int i : orderNumbers) {
					orderNumbersString += i + ",";
				}
				orderNumbersString = orderNumbersString.substring(0, orderNumbersString.lastIndexOf(","));
			} else {
				orderNumbersString = "ALL";
			}
			List<PeerMessage> resplist = this.connectAndSend(pd, REFRESH, fieldName + "~~~" + orderNumbersString, false);
			if (resplist == null || resplist.size() == 0) {
				// return;
			} else {
				String peerid = resplist.get(0).getMsgData();
				LoggerUtil.getLogger().fine("contacted " + peerid);
			}
		}
	}

	/* INNER CLASSES */

	/* msg syntax: JOIN pid host port */
	private class JoinHandler implements HandlerInterface {
		private Node	peer;

		public JoinHandler(Node peer) {
			this.peer = peer;
		}

		public void handleMessage(PeerConnection peerconn, PeerMessage msg) {
			if (peer.maxPeersReached()) {
				LoggerUtil.getLogger().fine("maxpeers reached " +
						peer.getMaxPeers());
				peerconn.sendData(new PeerMessage(ERROR, "Join: " +
						"too many peers"));
				return;
			}

			// check for correct number of arguments
			String[] data = msg.getMsgData().split("\\s");
			if (data.length != 3) {
				peerconn.sendData(new PeerMessage(ERROR, "Join: " +
						"incorrect arguments"));
				return;
			}

			// parse arguments into PeerInfo structure
			PeerInfo info = new PeerInfo(data[0], data[1],
					Integer.parseInt(data[2]));

			if (peer.getPeer(info.getId()) != null)
				peerconn.sendData(new PeerMessage(ERROR, "Join: " +
						"peer already inserted"));
			else if (info.getId().equals(peer.getId()))
				peerconn.sendData(new PeerMessage(ERROR, "Join: " +
						"attempt to insert self"));
			else {
				peer.addPeer(info);
				peerconn.sendData(new PeerMessage(REPLY, "Join: " +
						"peer added: " + info.getId()));
			}
		}
	}

	/* msg syntax: LIST */
	private class ListHandler implements HandlerInterface {
		private Node	peer;

		public ListHandler(Node peer) {
			this.peer = peer;
		}

		public void handleMessage(PeerConnection peerconn, PeerMessage msg) {
			peerconn.sendData(new PeerMessage(REPLY,
					String.format("%d", peer.getNumberOfPeers())));
			for (String pid : peer.getPeerKeys()) {
				peerconn.sendData(new PeerMessage(REPLY,
						String.format("%s %s %d", pid, peer.getPeer(pid).getHost(),
								peer.getPeer(pid).getPort())));
			}
		}
	}

	/* msg syntax: NAME */
	private class NameHandler implements HandlerInterface {
		private Node	peer;

		public NameHandler(Node peer) {
			this.peer = peer;
		}

		public void handleMessage(PeerConnection peerconn, PeerMessage msg) {
			peerconn.sendData(new PeerMessage(REPLY, peer.getId()));
		}
	}

	/* msg syntax: QUER return-pid key ttl */
	private class QueryHandler implements HandlerInterface {
		private P2Pnode	peer;

		public QueryHandler(P2Pnode p2Pnode) {
			this.peer = p2Pnode;
		}

		public void handleMessage(PeerConnection peerconn, PeerMessage msg) {
			String[] data = msg.getMsgData().split("\\s");
			if (data.length != 3) {
				peerconn.sendData(new PeerMessage(ERROR, "Query: incorrect arguments"));
				return;
			}

			String ret_pid = data[0].trim();
			String key = data[1].trim();
			int ttl = Integer.parseInt(data[2].trim());
			peerconn.sendData(new PeerMessage(REPLY, "Query: ACK"));
			/*
			 * After acknowledging the query, this connection will be closed. A
			 * separate thread will be started to actually perform the task of the
			 * query...
			 */

			QueryProcessor qp = new QueryProcessor(peer, ret_pid, key, ttl);
			qp.start();
		}
	}

	private class QueryProcessor extends Thread {
		private P2Pnode	peer;
		private String	ret_pid;
		private String	key;
		private int		ttl;

		public QueryProcessor(P2Pnode peer2, String ret_pid,
				String key, int ttl) {
			this.peer = peer2;
			this.ret_pid = ret_pid;
			this.key = key;
			this.ttl = ttl;
		}

		public void run() {
			// search through this node's list of files for a filename
			// containing the key
			for (String filename : peer.files.keySet()) {
				if (filename.toUpperCase().indexOf(key.toUpperCase()) >= 0) {
					String fpid = peer.files.get(filename);
					String[] data = ret_pid.split(":");
					String host = data[0];
					int port = Integer.parseInt(data[1]);
					peer.connectAndSend(new PeerInfo(ret_pid, host, port),
							QRESPONSE, filename + " " + fpid, true);
					LoggerUtil.getLogger().fine("Sent QRESP "
							+ new PeerInfo(ret_pid, host, port)
							+ " " + filename + " " + fpid);
					return;
				}
			}

			// will only reach here if key not found...
			// in which case propagate query to neighbors, if there is still
			// time-to-live for the query
			if (ttl > 0) {
				String msgdata = String.format("%s %s %d", ret_pid, key, ttl - 1);
				for (String nextpid : peer.getPeerKeys())
					peer.sendToPeer(nextpid, QUERY, msgdata, true);
			}
		}
	}

	/* msg syntax: RESP file-name pid */
	private class QResponseHandler implements HandlerInterface {
		@SuppressWarnings("unused")
		private Node	peer;

		public QResponseHandler(Node peer) {
			this.peer = peer;
		}

		public void handleMessage(PeerConnection peerconn, PeerMessage msg) {
			String[] data = msg.getMsgData().split("\\s");
			if (data.length != 2) {
				peerconn.sendData(new PeerMessage(ERROR, "Resp: "
						+ "incorrect arguments"));
				return;
			}

			String filename = data[0];
			String pid = data[1];
			if (files.containsKey(filename)) {
				peerconn.sendData(new PeerMessage(ERROR, "Resp: "
						+ "can't add duplicate file " + filename));
				return;
			}

			files.put(filename, pid);
			peerconn.sendData(new PeerMessage(REPLY, "Resp: "
					+ "file info added " + filename));
		}
	}

	/* msg syntax: QUIT pid */
	private class QuitHandler implements HandlerInterface {
		private Node	peer;

		public QuitHandler(Node peer) {
			this.peer = peer;
		}

		public void handleMessage(PeerConnection peerconn, PeerMessage msg) {
			String pid = msg.getMsgData().trim();
			if (peer.getPeer(pid) == null) {
				peerconn.sendData(new PeerMessage(ERROR, "Quit: peer not found: " + pid));
			}
			else {
				peer.removePeer(pid);
				peerconn.sendData(new PeerMessage(REPLY, "Quit: peer removed: " + pid));
			}
		}
	}

	/* msg syntax: REFRESH fieldName orderNumbers */
	private class RefreshHandler implements HandlerInterface {
		private Node	peer;

		public RefreshHandler(Node peer) {
			this.peer = peer;
		}

		public void handleMessage(PeerConnection peerconn, PeerMessage msg) {
			// String pid = msg.getMsgData().trim();
			String data = msg.getMsgData();
			String fieldName = data.split("~~~")[0];
			String data2 = data.split("~~~")[1];
			if (fieldName.equals("archived")) {
				fieldName = "NULL";
				GRFXOrdersMain.refreshRequest(fieldName, null, false);
			} else {
				if (!data2.equals("ALL")) {
					String[] orderNumStrings = data2.split("\\,");

					int[] orderNumbers = new int[orderNumStrings.length];
					for (int i = 0; i < orderNumStrings.length; i++) {
						orderNumbers[i] = Integer.parseInt(orderNumStrings[i]);
					}
					GRFXOrdersMain.refreshRequest(fieldName, orderNumbers, false);
				} else {
					GRFXOrdersMain.refreshRequest(fieldName, null, false);
				}
			}
		}
	}

	private class Router implements RouterInterface {
		private Node	peer;

		public Router(Node peer) {
			this.peer = peer;
		}

		public PeerInfo route(String peerid) {
			if (peer.getPeerKeys().contains(peerid))
				return peer.getPeer(peerid);
			else
				return null;
		}
	}
}
